#include "board.hpp"

bool Coord::operator== (const Coord &r)
{
    if (r.x == x && r.y == y)
        return true;
    return false;
}

bool Coord::operator!= (const Coord &r)
{
    if (r.x != x || r.y != y)
        return true;
    return false;
}

// Dir is a useful function for pawn movement. It gives the direction of
// movement the pawn should be heading to. By utilising this function, I no
// longer need to separate the if-function for each side, as to what I did
// for the pre 0.1 versions.
int dir(int side)
{
    if (side == PS_WHITE)
        return 1;
    else if (side == PS_BLACK)
        return -1;
    else
        return 0;
}

// -----------------------------------------------------------------------------
int Board::side_switch()
{
    if (side == PS_WHITE)
        side = PS_BLACK;
    else
        side = PS_WHITE;
    return side;
}
// -----------------------------------------------------------------------------
int Board::side_switch(int target)
{
    if (target == PS_WHITE)
        target = PS_BLACK;
    else
        target = PS_WHITE;
    return target;
}
// -----------------------------------------------------------------------------
Piece & Board::tiles(Coord target)
{
    if ((0 <= target.x && target.x < 8) && (0 <= target.y  && target.y < 8))
        return pieceset[target.x][target.y];
    else
        return pieceset[8][8];
}
// -----------------------------------------------------------------------------
Coord Board::find(Piece target, Coord start)
{
    for (int x = start.x; x < 8; x++)
        for (int y = start.y; y < 8; y++)
            if (tiles(Coord(x, y)) == target)
                return Coord(x, y);
    return Coord(8, 8);
}
// -----------------------------------------------------------------------------
int Board::capt (Coord targetpos)
{
    int originalside = side;
    if (tiles(targetpos) != Piece(0, 0))
    {
        side = side_switch(tiles(targetpos).x);
        for (int x = 0; x < 8; x++)
            for (int y = 0; y < 8; y++)
                if (chk(Coord(x, y), targetpos, true, PT_QUEEN) > 0)
                {
                    side = originalside;
                    return 1;
                }
    }
    side = originalside;
    return 0;
}
// -----------------------------------------------------------------------------
bool Board::blocked(Coord ref_from, Coord ref_diff)
{
    Coord step(0, 0);

    // Actually, what this does is to get -1 if the number is negative and
    // +1 if the number is positive. The if function is to prevent division
    // by 0.
    // This is needed to tell which direction to check towards
    if (ref_diff.x != 0)
        step.x = ref_diff.x / abs(ref_diff.x);
    if (ref_diff.y != 0)
        step.y = ref_diff.y / abs(ref_diff.y);

    // If the piece is moving horizontally or vertically, it would always
    // end up as the distance it needs to check till.
    int stop_at = abs(ref_diff.x) + abs(ref_diff.y);

    // However, if the piece is moving diagonally, the number would be
    // double of the distance, so we should correct that by dividing it
    // into half.
    if (abs(step.x) == abs(step.y))
        stop_at = stop_at / 2;

    // Loops along the distance one tile at a time
    for (int count = 1; count < stop_at; count++)
    {
        // This part is hard to explain, but trust me, it works.
        int point_x = ref_from.y + step.x * count;
        int point_y = ref_from.y + step.y * count;
        if (tiles(Coord(point_x, point_y)) != Piece(0, 0))
            return true;
    }
    return false;
}
// -----------------------------------------------------------------------------
int Board::chk(Coord ref_from, Coord ref_to, bool simulate, int promotion)
{
    // Old board layout so that we can revert on error
    Coord tempset[9][9];
    int tempenpassant[3];
    bool tempcastling[3][3];
    for (int x = 0; x < 8; x++)
        for (int y = 0; y < 8; y++)
            tempset[x][y] = pieceset[x][y];
    for (int x = 0; x < 3; x++)
        tempenpassant[x] = enpassant[x];
    for (int x = 0; x < 3; x++)
        for (int y = 0; y < 3; y++)
            tempcastling[x][y] = castling[x][y];
    int tempside = side;

    // Calculate the relative difference between source and destination
    Coord ref_diff = Coord(ref_to.x - ref_from.x, ref_to.x - ref_from.y);

    // Grab the piece info at the coordinates
    Piece ref_source = tiles(ref_from);
    Piece ref_dest = tiles(ref_to);

    // Assumed invalid until proven valid
    bool moveok = false;

    // Makes sure if it is moving at all
    if (ref_from == ref_to)
        return false;

    // You can't move nothing
    if (ref_source == Piece(0, 0))
        return false;

    // Nor can you pick other player's piece
    if (ref_source.x != side)
        return false;

    // And you cannot capture pieces of the same side
    if (ref_dest.x == ref_source.x)
        return false;

    switch (ref_source.y)
    {
    case PT_PAWN:
        // Using the direction function (dir), We can check if the pawn is
        // moving in the right direction it is supposed to be in.
        if (ref_diff.y == dir(side))
        {
            // Pawn can move forward one tile only if it is not taken
            if (ref_diff.x == 0 && ref_dest == Piece(0, 0))
            {
                moveok = true;
                break;
            }

            // If the pawn moves diagonally, it is only allowed on two
            // conditions:
            else if (abs(ref_diff.x) == 1)
            {
                // When capturing a piece
                if (ref_dest != Piece(0, 0))
                {
                    moveok = true;
                    break;
                }
                // When doing an En Passant
                else if (enpassant[side] == ref_to.x)
                    if (ref_to.y - 2 - side == 2 * dir(side))
                    {
                        if (!simulate)
                            tiles(Coord(ref_to.x, 5 - side)) = Piece (0, 0);
                        moveok = true;
                        break;
                    }
            }
        }

        // Otherwise, the only alternative is when it moves 2 tiles in the
        // right direction and not sideways.
        else if (ref_diff.y == dir(side) * 2 && ref_diff.x == 0)
        {
            // This is only allowed on the first move and that means that
            // the pawn must be in its starting row. It is a really
            // confusing formula to calculate that, and I forgot most of
            // the logic behind it. But at least it works.
            if (ref_from.y - 2 - side == -2 * dir(side))
                // It can't capture pieces in this move so that means that
                // the destination has to be empty.
                if (ref_dest == Piece(0, 0))
                    if (!blocked(ref_from, ref_diff))
                    {
                        // This opens up the oppurtunity for the En Passant.
                        if (!simulate)
                            enpassant[side_switch(side)] = ref_from.x;
                        moveok = true;
                        break;
                    }
        }
        break;
    case PT_QUEEN:
    case PT_CASTLE:
        // If moving horizontal or vertically, one of the difference in xy
        // coordinates must be 0. Since, anything times 0 is 0, if the two
        // values are multiplied together they should return 0.
        if (ref_diff.x * ref_diff.y == 0)
            if (!blocked(ref_from, ref_diff))
            {
                moveok = true;
                break;
            }
        // The queen can bypass this and continue into the BISHOP test;
        if (ref_source.y == PT_CASTLE)
        {
            if (!simulate)
                if (ref_source.y == (side - 1) * 7)
                {
                    if (ref_source.x == 0) // Queen side
                        castling[side][0] = false;
                    if (ref_source.x == 7) // King side
                        castling[side][2] = false;
                }
            break;
        }
    case PT_BISHOP:
        // Any diagonal movement must have a equal value of xy difference
        // when both are passed through an absolute function.
        if (abs(ref_diff.x) == abs(ref_diff.y))
            if (!blocked(ref_from, ref_diff))
            {
                moveok = true;
                break;
            }
        break;
    case PT_KNIGHT:
        // The L shape allowed uses the combination of (1,2) and or (2,1).
        // The sum of the x and y coordinates is always 3. That is what the
        // first "if" test checks. However, there are two other combinations
        // possible. They are (0,3) and (3,0). The second function makes
        // sure there is no 0 in either two combinations so filtering them
        // out.
        if (abs(ref_diff.x) + abs(ref_diff.y) == 3)
            if (ref_diff.y * ref_diff.y != 0)
            {
                moveok = true;
                break;
            }
        break;
    case PT_KING:
        // The king can move one tile in any direction, including
        // diagonally.
        if (abs(ref_diff.x) <= 1 && abs(ref_diff.y) <= 1)
        {
            moveok = true;
            break;
        }
        // If this is a castling move
        else if (abs(ref_diff.x) == 2 && ref_diff.y == 0)
        {
            // Actually, what this does is to get the direction on the x
            // axis of where the King is headed
            int cdir = ref_diff.x / abs(ref_diff.x);

            // Castle's Position
            Coord cpos;
            if (cdir == -1) // Queen Side
                cpos = Coord(0, ref_from.y);
            else if (cdir == 1) // King Side
                cpos = Coord(7, ref_from.y);

            // The midway point of where the King is headed to
            Coord midway = Coord(ref_from.x + cdir, cpos.y);

            // Castling for the side cannot be used before
            if (castling[side][cdir + 1])
                //
                // The move cannot be blocked
                if (!blocked(cpos, Coord(cpos.x - midway.x, cpos.y - midway.y)))
                {
                    //
                    // No need to worry about infinitely recursive function
                    // as it would not happen here
                    if (chk(ref_from, midway, true, PT_QUEEN))
                    {
                        side = tempside;
                        if (chk(midway, ref_dest, true, PT_QUEEN))
                        {
                            moveok = true;
                        }
                    }
                    // Revert
                    for (int x = 0; x < 8; x++)
                        for (int y = 0; y < 8; y++)
                            pieceset[x][y] = tempset[x][y];
                    for (int x = 0; x < 3; x++)
                        enpassant[x] = tempenpassant[x];
                    for (int x = 0; x < 3; x++)
                        for (int y = 0; y < 3; y++)
                            castling[x][y] = tempcastling[x][y];
                    side = tempside;
                    side = tempside;
                    if (!simulate && moveok)
                    {
                        // Moving the King would be done, but not
                        // the Castle, so do it here
                        tiles(midway) = tiles(cpos);
                        tiles(cpos) = Piece(0, 0);
                        break;
                    }
                }
        }
        if (!simulate)
        {
            castling[side][0] = false;
            castling[side][2] = false;
        }
        break;
    }

    // No invalid moves can go beyond this point
    if (!moveok)
        return false;

    // If this is not a simulation, then do the actual move
    if (!simulate)
    {
        // If did a turn is passed, whether or not En Passant is used, it
        // becomes invalid and unusable.
        enpassant[side] = -1;
        //
        // Move the piece to the new position and clear the original
        // position
        tiles(ref_to) = tiles(ref_from);
        tiles(ref_from) = Piece(0, 0);
        if (promotion < PT_QUEEN || promotion > PT_BISHOP)
            promotion = PT_QUEEN;
        if (ref_source.y == PT_PAWN)
            if (ref_to.y == 7 - (side - 1) * 7)
                tiles(ref_to) = Piece(side, promotion);
        //
        // If the King can be captured
        if (capt(find(Piece(side, PT_KING))))
        {
            // Revert
            for (int x = 0; x < 8; x++)
                for (int y = 0; y < 8; y++)
                    pieceset[x][y] = tempset[x][y];
            for (int x = 0; x < 3; x++)
                enpassant[x] = tempenpassant[x];
            for (int x = 0; x < 3; x++)
                for (int y = 0; y < 3; y++)
                    castling[x][y] = tempcastling[x][y];
            side = tempside;
            return false;  // Definitely wrong so end it here
        }
        //
        // Now it is the other player's turn.
        side_switch();
    }
    return true;
}
// -----------------------------------------------------------------------------
// Counts the number of possible moves allowed
int board_possible(Board target, int side)
{
    target.side = side;
    int possible = 0;
    Board tboard;
    for (int x = 0; x < 8; x++)
        for (int y = 0; y < 8; y++)
            for (int u = 0; u < 8; u++)
                for (int v = 0; v < 8; v++)
                {
                    tboard = target;
                    if (tboard.chk(Coord(x, y), Coord(u, v), false, PT_QUEEN))
                    {
                        possible++;
                    }
                }
    return possible;
}
